/*
 * function.c
 *
 *  Created on: 2011-8-20
 *      Author: SL400
 */
#include <stdio.h>
#include <memory.h>
#include <stdlib.h>
#include "consts.h"
#include "common.h"
#include "sys_struct.h"
#include "function.h"
#include "disks.h"
#include "syscall.h"
/*
 * fill an inode_disk struct from an inode_memory struct.
 * */
void fill_inode_disk(inode_disk *i1, inode_memory *i2)
{
	i1->i_create_time = i2->i_create_time;
	i1->i_modify_time = get_system_time();
	i1->i_mode = i2->i_mode;
	i1->size = i2->size;
	memcpy( i1->data_addr, i2->data_addr, sizeof(dword) * 4);
}

/*
 * fill and inode_memory structure from an inode_disk structure.
 * */
void fill_inode_memory(inode_memory *i2, inode_disk *i1, int inode_no)
{
	memcpy(i2->data_addr, i1->data_addr, sizeof(dword) * 4);
	i2->i_create_time = i1->i_create_time;
	i2->i_modify_time = i1->i_modify_time;
	i2->i_mode = i1->i_mode;
	i2->inode_lock = 0;
	i2->i_no_disk = inode_no;
	i2->size = i1->size;
	i2->count = 0;
}

void list_append(data_memory *list, data_memory *item)
{
	item->last = list->last;
	list->last = item;
	item->next = list;
	item->last->next = item;
}

data_memory* list_remove(data_memory *list)
{
	data_memory* item = list->next;
	list->next = item->next;
	list->next->last = list;

	return item;
}

int list_empty(data_memory *list)
{
	return list->next == list;
}

void fill_path_info(path_info *pi, int status, int fd_parent, int ino_c, const char* p)
{
	pi->status = status;
	pi->fd_p = fd_parent;
	pi->ino_c = ino_c;
	pi->p = p;
}

/*
 * load one block from the disk into memory.
 * block_no: the block number.
 * return: the index of the data block buffer.
 * */
static int block_load(dword block_no)
{
	data_memory* item;
	if ( freelist.next == &freelist )
	{
		item = list_remove( &busylist );
		write_block(0, item->block_no, buffer[item->buffer_index]);
	}
	else
		item = list_remove( &freelist );

    item->block_no = block_no;
	read_block(0, block_no, buffer[item->buffer_index]);
	list_append( &busylist, item );
	return item->buffer_index;
}
/*
 * switch content of buffer and disk block
 * rw points out switching is reading or writing. 1 for read, 0 for write
 *
 * return: 1 for success, 0 for error.
 * */
int readwrite(void* bf, dword block_no, int offset, int size, int rw)
{
	data_memory* iter;
	int bf_index = -1;

	if ( offset + size >= BLOCK_SIZE ) return -1;
	for ( iter=busylist.next; iter!=&busylist; iter=iter->next)
	{
		if ( iter->block_no == block_no ) {
			bf_index = iter->buffer_index;
            break;
		}
	}
	if ( iter==&busylist)
		bf_index = block_load(block_no);

	if ( rw == RW_READ )
		memcpy( bf, buffer[bf_index]+offset, size);
	else
		memcpy( buffer[bf_index]+offset, bf, size);
	return 1;
}

int rw_inode(inode_disk* idisk, int ino, int rw)
{
    int blkno = ino / BKINODS + 2;
    int offset = ino % BKINODS * INODE_SIZE;
    return readwrite(idisk, blkno, offset, INODE_SIZE, rw);
}

static int search_new_blocks()
{
    static int tmp_blk[128], i;
    dword current = filesys.data_buffer[i];
    readwrite(tmp_blk, current, 0, BLOCK_SIZE, RW_READ);
    if ( tmp_blk[0]!= -1 ){
        for (i=0; i<tmp_blk[0]; i++)
            filesys.data_buffer[i] = tmp_blk[i+1];
        filesys.n_data_cnt = tmp_blk[0];
    }
    else filesys.n_data_cnt = 0;
    return current;
}

int allocate_block()
{
    int ret;

    /*fail if no block is free*/
    if ( filesys.n_data_cnt == 0 )
        return -1;
    else {
        filesys.f_block_modify = 1;
        if ( filesys.n_data_cnt <=1 )
            return search_new_blocks();
        return filesys.data_buffer[--filesys.n_data_cnt];
    }
}

void leave_system()
{
    data_memory* item;
    inode_disk id;
    int i, number;

    for (i=0; i<INODE_BF_SIZE; i++)
    {
    	number = inode_buffers[i].i_no_disk;
    	if (number!= -1 ){
    		fill_inode_disk( &id, &inode_buffers[i]);
    		rw_inode(&id, number, RW_WRITE);
    	}
    }

    for (item = busylist.next; item!= &busylist; item = item->next )
        write_block(0, item->block_no, buffer[item->buffer_index]);
    if ( filesys.f_block_modify == 1)
        write_blocks(0, 0, 2, &filesys);

}

int get_phy_no(inode_memory* inode, int logic_no)
{
    int tmp = BLOCK_SIZE / sizeof(dword);
    int result;
    
    if ( logic_no * BLOCK_SIZE > inode->size ) return 0;
    if ( IS_SMALL(inode->i_mode) )
        return inode->data_addr[logic_no];
    else {
        readwrite(&result, inode->data_addr[logic_no/tmp],
                  logic_no%tmp*4, 4, RW_READ);
        return result;
    }
}

/*
 * get the free inode numbers from disk, then fill them into the super_block structure.
 * */
static void search_free_inodes()
{
	int i, bound, j;
	static inode_disk bf_blks[128];

	for (i=0; i<filesys.n_inode_blks; i+=8 )
	{
		if ( i+8 >= filesys.n_inode_blks )
			bound = filesys.n_inode_blks;
		else bound = 8;
		read_blocks(0, i + 2, bound, bf_blks);

		for ( j=0; j < bound * 16; j++)
		{
			if ( IS_OCCUPY(bf_blks[j].i_mode)==0 )
				filesys.inode_buffer[filesys.n_inode_cnt ++ ] = i * 128 + j;
			if ( filesys.n_inode_cnt > BLK_BF_SIZE ) break;
		}

		if ( filesys.n_inode_cnt >BLK_BF_SIZE ) break;
	}
}

/*
 * allocate a free inode . return the number.
 * */
int allocate_inode()
{
	if ( filesys.n_inode_cnt <= 0 )
		search_free_inodes();
    if ( filesys.n_inode_cnt <= 0 )
        return -1;
	return filesys.inode_buffer[ --filesys.n_inode_cnt ];
}

/*
 * pick a free file_handle and return the index.
 * */
int allocate_handle()
{
    int i;
    for (i=0; i<FH_SIZE; i++){
        if ( file_handles[i].inode_no==-1)
            return i;
    }

    return -1;
}

/*allocate a free block for an inode.
 * return the allocated block number. Fail for -1*/
int allocate_data(inode_memory* im)
{
	int blk_no1 = allocate_block(), blk_no2, offset;
    if ( blk_no1 == -1 ) return -1;

    if ( im->size <= 3*BLOCK_SIZE ){
        im->data_addr[ im->size / BLOCK_SIZE ] = blk_no1;
    }

    else if ( im->size / BLOCK_SIZE == 4 )
    {
    	blk_no2 = allocate_block();
    	if ( blk_no2 == -1 ) {free_block(blk_no1); return -1;}
    	else {
            im->i_mode &= M_BIGF;
    		readwrite(im->data_addr, blk_no2, 0, sizeof(dword) * 4, RW_WRITE);
    		readwrite(&blk_no1, blk_no2, sizeof(dword)*4, 4, RW_WRITE);
    		im->data_addr[0] = blk_no2;
    	}
    }
    else if ( im->size / BLOCK_SIZE %128 == 0)
    {
    	blk_no2 = allocate_block();
    	if ( blk_no2 == -1 ){ free_block(blk_no1); return -1;}
    	else {
    		readwrite(&blk_no1, blk_no2, 0, 4, RW_WRITE);
    		im->data_addr[ im->size / BLOCK_SIZE / 128 ] = blk_no2;
    	}
    }
    else
    {
    	blk_no2 = im->data_addr[im->size / BLOCK_SIZE / 128];
    	offset = im->size / BLOCK_SIZE % 128;
    	readwrite( &blk_no1, blk_no2, offset, 4, RW_WRITE);
    }

    return blk_no1;
}

int free_block(dword block_no)
{
	if ( filesys.n_data_cnt < BLK_BF_SIZE )
		filesys.data_buffer[filesys.n_data_cnt ++ ] = block_no;
	else {
		readwrite( &filesys.n_data_cnt, block_no, 0, sizeof(int), RW_WRITE);
		readwrite(filesys.data_buffer, block_no, sizeof(int), filesys.n_data_cnt*sizeof(int), RW_WRITE);
		filesys.n_data_cnt = 1;
		filesys.data_buffer[0] = block_no;
	}

	return 0;
}

void free_inode(int inode)
{
    inode_disk* idisk;
    if ( filesys.n_inode_cnt<BLK_BF_SIZE )
        filesys.inode_buffer[filesys.n_inode_cnt++] = inode;
    else
    {
        idisk = (inode_disk*)malloc( sizeof(inode_disk) );
        idisk->i_mode &= ~M_OCCUPY;
        rw_inode(idisk, inode, RW_WRITE);
        free(idisk);
    }
}

void free_data(inode_memory* im)
{
    int blks = ( im->size + BLOCK_SIZE - 1 ) / BLOCK_SIZE;
    int i, no;
    for (i=0; i<blks; i++){
        no = get_phy_no(im, i);
        free_block(no);
    }

    if ( blks > 4 )
    {
        blks = (blks + 127) / 128;
        for (i=0; i<blks; i++)
            free_block(im->data_addr[i]);
    }

    im->size = 0;
    im->i_modify_time = get_system_time();
    im->i_mode &= ~M_BIGF;
}

static int get_ibuffer(int ino)
{
    inode_memory* ibuffer;
    inode_disk idisk;
    int i;
    int temp1=-1, temp2=-1;
    for (i=0; i<INODE_BF_SIZE; i++)
    {
        if ( inode_buffers[i].i_no_disk == ino )
            return i;
        else if ( inode_buffers[i].i_no_disk == -1 ) { temp1 = i; }
        else if ( inode_buffers[i].count == 0 ){ temp2 = i; }
    }

    if ( temp1!=-1 ) return temp1;
    if ( temp2!=-1 )
    {
        ibuffer = &inode_buffers[temp2];
        fill_inode_disk( &idisk, ibuffer);
        rw_inode(&idisk, ibuffer->i_no_disk, RW_WRITE);
        return temp2;
    }
    return -1;
}

int load_inode(int ino_disk)
{
    inode_disk idisk;
    
    int index = get_ibuffer(ino_disk);
    if (index == -1) return -1;
    if ( inode_buffers[index].i_no_disk == ino_disk ) return index;
    if (rw_inode(&idisk, ino_disk/16, RW_READ) == -1)
    	return -1;
    fill_inode_memory( &inode_buffers[index], &idisk, ino_disk);
    return index;
}
/*
 *
 * */
int create_inode()
{
    int i , j;

    i = allocate_inode();
    if ( i==-1 ) return -1;
    j = get_ibuffer(-1);
    if ( j==-1 ) { free_inode(i); return -1; }
    inode_buffers[j].i_no_disk = i;
    inode_buffers[j].i_create_time = get_system_time();
    
    return j;
}

void edit_dir(char* dir, int type, int ino)
{
    int len;
    if ( type == ET_UP )
    {
        while ( current_dir.len > 1 && current_dir.dir[--current_dir.len]!='/' );
        current_dir.dir[current_dir.len]=0;
    }
    else
    {
        if ( type == ET_MODIFY )
            current_dir.len = 0;
        len = strlen(dir);
        if ( len + current_dir.len >  current_dir.size ) {
            current_dir.size = (len + current_dir.len) * 2;
            current_dir.dir = (char*)realloc( current_dir.dir, current_dir.size );
        }
        strcpy(current_dir.dir + len, dir);
        current_dir.len += len;
    }

    current_dir.ino = ino;
}
